<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Model Config Center</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <style>
      :root{
        --bg:#f6f8fb; --card:#ffffff; --muted:#6b7280; --fg:#111827; --pri:#2E64E8; --pri-2:#20B2AA; --bd:#e5e7eb; --ok:#16a34a; --err:#dc2626;
      }
      *{box-sizing:border-box}
      html,body{height:100%}
      body{margin:0;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;color:var(--fg);
           background:linear-gradient(135deg,#0A59F7 0%, #2E64E8 55%, #1FA2FF 100%);} /* 整页蓝色渐变背景 */

      .topbar{background:linear-gradient(to right, #2E64E8, #38bdf8, #20B2AA);box-shadow:0 2px 8px rgba(0,0,0,.08)}
      .topbar-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;padding:24px 28px}
      .brand{display:flex;gap:16px;align-items:center}
      .logo{width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,#0A59F7,#38bdf8);box-shadow:0 2px 6px rgba(0,0,0,.15)}
      .title h1{margin:0;font-size:24px;font-weight:700;color:#fff}
      .title p{margin:4px 0 0;color:rgba(255,255,255,.85);font-size:13px;font-weight:500}
      .container{max-width:1200px;margin:28px auto;padding:0 28px}
      .actions{display:flex;gap:12px;align-items:center}
      .user-info{display:flex;gap:12px;align-items:center;color:#fff;font-weight:600;font-size:14px}
      .role-badge{padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.2);font-size:12px}
      .btn{border:1px solid rgba(255,255,255,.3);background:rgba(255,255,255,.15);backdrop-filter:blur(8px);padding:10px 16px;border-radius:8px;color:#fff;cursor:pointer;font-weight:600;font-size:14px}
      .btn:hover{border-color:rgba(255,255,255,.5);background:rgba(255,255,255,.25)}
      .btn-primary{background:rgba(255,255,255,.95);border-color:#fff;box-shadow:0 4px 12px rgba(0,0,0,.15);color:#2E64E8;font-weight:700}
      .btn-primary:hover{background:#fff;box-shadow:0 6px 16px rgba(0,0,0,.2)}
      .grid{display:grid;grid-template-columns:1.2fr .8fr;gap:20px}
      .card{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:24px;box-shadow:0 2px 8px rgba(0,0,0,.04)}
      h2{margin:0 0 16px;font-size:18px;font-weight:700;color:#111827}
      .muted{color:var(--muted);font-size:13px;font-weight:500}
      table{width:100%;border-collapse:separate;border-spacing:0 10px}
      thead th{color:#374151;font-weight:700;text-align:left;font-size:13px;padding:0 12px}
      tbody tr{background:#fff;border:1px solid var(--bd);border-radius:10px}
      tbody td{padding:14px 12px;vertical-align:middle;border-bottom:1px solid transparent;font-weight:500;font-size:13px}
      .pill{padding:5px 10px;border-radius:999px;border:1px solid #dbeafe;background:#eff6ff;color:#1d4ed8;font-size:13px;font-weight:600}
      .prio{padding:5px 10px;border-radius:999px;font-size:12px;font-weight:800}
      .prio-high{background:#fee2e2;color:#b91c1c;border:1px solid #fecaca}
      .prio-medium{background:#fef9c3;color:#92400e;border:1px solid #fde68a}
      .prio-low{background:#dcfce7;color:#166534;border:1px solid #bbf7d0}
      .row-actions{display:flex;gap:8px}
      .input{width:100%;border:1px solid var(--bd);background:#fff;color:var(--fg);padding:12px 14px;border-radius:8px;font-size:14px;font-weight:500}
      label{display:block;margin:14px 0 8px;font-weight:700;font-size:13px;color:#2E64E8}
      .form-row{display:grid;grid-template-columns:1fr 1fr;gap:14px}
      .toast{position:fixed;top:24px;right:24px;background:#111827;color:#fff;border:1px solid #2E64E8;padding:12px 16px;border-radius:8px;display:none;font-weight:600;font-size:14px;z-index:1000}
      .ok{color:#86efac} .err{color:#fecaca}
      .footer{margin-top:14px;color:var(--muted);font-size:13px;text-align:right;font-weight:500}
      /* --- Product Header Bar --- */
      .product-header{position:fixed;top:0;left:0;right:0;background:rgba(255,255,255,.08);backdrop-filter:blur(12px);border-bottom:1px solid rgba(255,255,255,.15);z-index:100;padding:16px 0}
      .product-header-inner{max-width:1200px;margin:0 auto;display:flex;align-items:center;justify-content:center;gap:14px;padding:0 24px}
      .product-logo{width:38px;height:38px;border-radius:10px;background:linear-gradient(135deg,#fff 0%, rgba(255,255,255,.85) 100%);box-shadow:0 2px 8px rgba(0,0,0,.15);display:flex;align-items:center;justify-content:center;font-size:20px}
      .product-title{color:#fff;font-size:20px;font-weight:900;letter-spacing:.3px;margin:0}
      .product-desc{color:rgba(255,255,255,.88);font-size:13px;font-weight:600;margin:0 0 0 12px;padding-left:12px;border-left:1px solid rgba(255,255,255,.25)}
      /* --- Auth (centered, enlarged) --- */
      .auth-screen{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:100px 20px 40px}
      .auth-form{width:94%;max-width:520px;background:rgba(255,255,255,.9);backdrop-filter:blur(10px);border:1px solid rgba(46,100,232,.25);border-radius:20px;box-shadow:0 24px 72px rgba(10,89,247,.25)}
      .auth-head{display:flex;align-items:center;gap:14px;padding:22px 26px;border-bottom:1px solid var(--bd)}
      .auth-logo{width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#0A59F7,#38bdf8)}
      .auth-title{margin:0;font-weight:900;letter-spacing:.2px}
      .auth-sub{margin:4px 0 0;color:#4b5563;font-weight:600;font-size:12px}
      .auth-body{padding:26px 28px}
      .auth-tabs{display:flex;gap:10px;background:#f3f4f6;border:1px solid #e5e7eb;padding:6px;border-radius:10px;margin-bottom:18px}
      .auth-tab{flex:1;padding:10px 12px;cursor:pointer;border:none;background:transparent;font-weight:800;font-size:13px;color:#6b7280;border-radius:8px}
      .auth-tab.active{color:#0f172a;background:#fff;box-shadow:0 1px 0 rgba(15,23,42,.04)}
      .input-wrap{position:relative}
      .input-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#64748b}
      .input.input-with-icon{padding-left:38px}
      .hint{margin-top:6px;color:#6b7280;font-size:12px;font-weight:600}
      .btn-gradient{background:linear-gradient(90deg,#2E64E8,#20B2AA);color:#fff;border:0}
      .btn-gradient:hover{filter:saturate(1.1)}
      .hidden{display:none}
    </style>
  </head>
  <body>
    <div id="authView" class="auth-screen">
      <div class="product-header">
        <div class="product-header-inner">
          <div class="product-logo">⚡</div>
          <h1 class="product-title">OpenHarmony · Model Config Center</h1>
          <p class="product-desc">统一、安全、优雅的 LLM 模型与密钥管理中心</p>
        </div>
      </div>
      <div class="auth-form">
          <div class="auth-head">
            <div class="auth-logo"></div>
            <div>
              <div class="auth-title">登录到控制台</div>
              <div class="auth-sub">首次使用可直接注册管理员账号</div>
            </div>
          </div>
          <div class="auth-body">
            <div class="auth-tabs">
              <button class="auth-tab active" onclick="switchTab('login')">登录</button>
              <button class="auth-tab" onclick="switchTab('register')">注册</button>
            </div>
            <div id="loginForm">
              <label>用户名</label>
              <div class="input-wrap">
                <span class="icon">👤</span>
                <input class="input input-with-icon" id="loginUsername" placeholder="请输入用户名" />
              </div>
              <label>密码</label>
              <div class="input-wrap">
                <span class="icon">🔒</span>
                <input class="input input-with-icon" id="loginPassword" type="password" placeholder="请输入密码" />
              </div>
              <button class="btn btn-gradient" style="width:100%;margin-top:20px" onclick="doLogin()">登录</button>
            </div>
            <div id="registerForm" class="hidden">
              <label>用户名</label>
              <div class="input-wrap">
                <span class="icon">👤</span>
                <input class="input input-with-icon" id="regUsername" placeholder="至少3个字符" />
              </div>
              <label>密码</label>
              <div class="input-wrap">
                <span class="icon">🔒</span>
                <input class="input input-with-icon" id="regPassword" type="password" placeholder="至少6个字符" />
              </div>
              <label>角色</label>
              <select class="input" id="regRole">
                <option value="DEVELOPER">开发者（仅浏览）</option>
                <option value="ADMIN">管理员（可配置）</option>
              </select>
              <div class="hint">管理员可新增/编辑/删除模型配置；开发者仅可浏览。</div>
              <button class="btn btn-gradient" style="width:100%;margin-top:20px" onclick="doRegister()">注册</button>
            </div>
          </div>
      </div>
    </div>

    <div id="mainView" class="hidden">
      <div class="topbar">
        <div class="topbar-inner">
          <div class="brand">
            <div class="logo"></div>
            <div class="title">
              <h1>OpenHarmony · Model Config Center</h1>
              <p>集中管理 LLM 模型与密钥（密钥仅服务端加密存储）</p>
            </div>
          </div>
          <div class="actions">
            <div class="user-info" id="userInfo"></div>
            <button class="btn" onclick="refresh()">刷新</button>
            <button class="btn btn-primary" id="saveBtn" onclick="submitForm()">保存配置</button>
            <button class="btn" onclick="logout()">退出</button>
          </div>
        </div>
      </div>

      <div class="container">
        <div class="grid">
          <div class="card">
            <h2>已注册模型</h2>
            <p class="muted">密钥不会通过接口返回。仅展示模型元信息。</p>
            <div style="overflow:auto; max-height:460px; margin-top:10px">
              <table>
                <thead><tr><th>name</th><th>model</th><th>url</th><th>description</th><th>priority</th><th style="width:120px" id="actionHeader">操作</th></tr></thead>
                <tbody id="list"></tbody>
              </table>
            </div>
          </div>

          <div class="card" id="configCard">
            <h2>新增 / 更新</h2>
            <div class="form">
              <label>显示名称 name</label>
              <input class="input" id="name" placeholder="如 glm-4.6" />
              <div class="form-row">
                <div>
                  <label>模型标识 model</label>
                  <input class="input" id="model" list="modelOptions" placeholder="glm-4.6 / glm-4-plus / glm-4-long ..." />
                  <datalist id="modelOptions">
                    <option value="glm-4.6" />
                    <option value="glm-4-plus" />
                    <option value="glm-4-long" />
                    <option value="glm-4.5" />
                    <option value="glm-4.5v" />
                    <option value="glm-4.5-air" />
                  </datalist>
                </div>
                <div>
                  <label>API Key</label>
                  <input class="input" id="apiKey" placeholder="将进行加密存储" />
                </div>
              </div>
              <label>优先级 priority</label>
              <select class="input" id="priority">
                <option value="HIGH">HIGH（高优先级）</option>
                <option value="MEDIUM" selected>MEDIUM（中）</option>
                <option value="LOW">LOW（低）</option>
              </select>
              <label>接口地址 url</label>
              <input class="input" id="url" placeholder="https://open.bigmodel.cn/api/paas/v4/chat/completions" />
              <label>描述 description</label>
              <input class="input" id="description" placeholder="可选" />
              <div class="footer">保存成功后，左侧列表将实时更新</div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="toast" id="toast"></div>

    <script>
      let token = localStorage.getItem('token') || '';
      let userRole = localStorage.getItem('role') || '';
      let username = localStorage.getItem('username') || '';

      const api = (p='') => `${location.origin}/api${p}`;
      const el = id => document.getElementById(id);
      function toast(msg, ok=true){ const t=el('toast'); t.className='toast ' + (ok?'ok':'err'); t.textContent=msg; t.style.display='block'; setTimeout(()=>t.style.display='none', 2600); }

      function checkAuth(){
        if(token){
          el('authView').classList.add('hidden');
          el('mainView').classList.remove('hidden');
          updateUI();
          refresh();
        } else {
          el('authView').classList.remove('hidden');
          el('mainView').classList.add('hidden');
        }
      }

      function updateUI(){
        el('userInfo').innerHTML = `<span>${username}</span><span class="role-badge">${userRole === 'ADMIN' ? '管理员' : '开发者'}</span>`;
        if(userRole !== 'ADMIN'){
          el('configCard').classList.add('hidden');
          el('saveBtn').classList.add('hidden');
          document.querySelectorAll('.row-actions').forEach(e => e.innerHTML = '');
        } else {
          el('configCard').classList.remove('hidden');
          el('saveBtn').classList.remove('hidden');
        }
      }

      function switchTab(type){
        document.querySelectorAll('.auth-tab').forEach(b => b.classList.remove('active'));
        event.target.classList.add('active');
        el('loginForm').classList.toggle('hidden', type !== 'login');
        el('registerForm').classList.toggle('hidden', type !== 'register');
      }

      async function doLogin(){
        const res = await fetch(api('/auth/login'), {
          method:'POST', headers:{'Content-Type':'application/json'},
          body: JSON.stringify({username:el('loginUsername').value.trim(), password:el('loginPassword').value.trim()})
        });
        if(res.ok){
          const data = await res.json();
          token = data.token; userRole = data.role; username = data.username;
          localStorage.setItem('token', token); localStorage.setItem('role', userRole); localStorage.setItem('username', username);
          toast('登录成功');
          checkAuth();
        } else {
          const err = await res.json().catch(()=>({message:'登录失败'}));
          toast(err.message || '登录失败', false);
        }
      }

      async function doRegister(){
        const res = await fetch(api('/auth/register'), {
          method:'POST', headers:{'Content-Type':'application/json'},
          body: JSON.stringify({
            username:el('regUsername').value.trim(),
            password:el('regPassword').value.trim(),
            role:el('regRole').value
          })
        });
        if(res.ok){
          const data = await res.json();
          token = data.token; userRole = data.role; username = data.username;
          localStorage.setItem('token', token); localStorage.setItem('role', userRole); localStorage.setItem('username', username);
          toast('注册成功');
          checkAuth();
        } else {
          const err = await res.json().catch(()=>({message:'注册失败'}));
          toast(err.message || '注册失败', false);
        }
      }

      function logout(){
        token = ''; userRole = ''; username = '';
        localStorage.removeItem('token'); localStorage.removeItem('role'); localStorage.removeItem('username');
        checkAuth();
      }

      function getHeaders(){
        return token ? {'Content-Type':'application/json', 'Authorization':`Bearer ${token}`} : {'Content-Type':'application/json'};
      }

      async function refresh(){
        const res = await fetch(api('/models'), {headers:getHeaders()});
        if(res.status === 401){ logout(); return; }
        const data = await res.json();
        const tbody = el('list');
        tbody.innerHTML='';
        for(const m of data){
          const tr = document.createElement('tr');
          const actions = userRole === 'ADMIN' ? 
            `<button class="btn" onclick="prefill('${m.name}')">编辑</button><button class="btn" onclick="doDelete('${m.name}')">删除</button>` : '';
          const pr = (m.priority||'MEDIUM').toUpperCase();
          const prClass = pr==='HIGH'?'prio prio-high':(pr==='LOW'?'prio prio-low':'prio prio-medium');
          tr.innerHTML = `
            <td><span class="pill">${m.name}</span></td>
            <td>${m.model}</td>
            <td><span class="muted">${m.url}</span></td>
            <td>${m.description||''}</td>
            <td><span class="${prClass}">${pr}</span></td>
            <td class="row-actions">${actions}</td>`;
          tbody.appendChild(tr);
        }
      }

      async function submitForm(){
        const body = {
          name: el('name').value.trim(),
          model: el('model').value.trim(),
          url: el('url').value.trim(),
          apiKey: el('apiKey').value.trim(),
          description: el('description').value.trim(),
          priority: el('priority').value
        };
        if(!body.name || !body.model || !body.url || !body.apiKey){ toast('请完整填写必填项', false); return; }
        const res = await fetch(api('/models'), { method:'POST', headers:getHeaders(), body: JSON.stringify(body)});
        if(res.ok){ toast('保存成功'); el('apiKey').value=''; await refresh(); }
        else { 
          if(res.status === 401){ logout(); return; }
          const err = await res.json().catch(()=>({message:res.status}));
          toast('失败: '+(err.message||res.status), false);
        }
      }

      async function doDelete(name){
        if(!confirm(`确认删除 ${name} ?`)) return;
        const res = await fetch(api('/models/'+encodeURIComponent(name)), { method:'DELETE', headers:getHeaders() });
        if(res.status === 401){ logout(); return; }
        if(res.status===204){ toast('已删除'); refresh(); } else { toast('删除失败', false); }
      }

      async function prefill(name){
        const res = await fetch(api('/models/'+encodeURIComponent(name)), {headers:getHeaders()});
        if(res.status === 401){ logout(); return; }
        if(!res.ok){ toast('读取失败', false); return; }
        const m = await res.json();
        el('name').value = m.name;
        el('model').value = m.model;
        el('url').value = m.url;
        el('description').value = m.description||'';
        el('priority').value = (m.priority||'MEDIUM').toUpperCase();
        el('apiKey').value = '';
        toast('已加载配置，可更新');
      }

      checkAuth();
    </script>
  </body>
</html>
